package com.join.mybatis.method;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.SqlCondition;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils;
import com.join.mybatis.enums.MySqlMethod;
import com.join.mybatis.mapping.MyMapperRegister;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import static java.util.stream.Collectors.joining;

/**
 * @author chengang
 */
public class JoinMethod extends AbstractMethod {

    private JoinColumnConfig joinClumnConfig;

    private String tableStr;

    private MyMapperRegister myMapperRegister=new MyMapperRegister();


    @Override
    protected String sqlSelectColumns(TableInfo table, boolean queryWrapper) {
        String selectColumns = ASTERISK;
        return createSqlSelectClumn(selectColumns,table);
    }

    private String getAllSqlWhere(TableInfo tableInfo, boolean ignoreLogicDelFiled, boolean withId, final String prefix) {
        final String newPrefix = prefix == null ? EMPTY : prefix;
        String filedSqlScript = tableInfo.getFieldList().stream()
                .filter(i -> {
                    if (ignoreLogicDelFiled) {
                        return !(tableInfo.isLogicDelete() && i.isLogicDelete());
                    }
                    return true;
                })
                .map(i -> getSqlWhere(tableInfo,i,newPrefix)).filter(Objects::nonNull).collect(joining(NEWLINE));
        if (!withId || StringUtils.isBlank(tableInfo.getKeyProperty())) {
            return filedSqlScript;
        }
        String newKeyProperty = newPrefix + tableInfo.getKeyProperty();
        String keySqlScript = String.format("%s.%s",tableInfo.getTableName(),tableInfo.getKeyColumn()+ EQUALS + SqlScriptUtils.safeParam(newKeyProperty));
        return SqlScriptUtils.convertIf(keySqlScript, String.format("%s != null", newKeyProperty), false)
                + NEWLINE + filedSqlScript;
    }

    private String getSqlWhere(TableInfo tableInfo, TableFieldInfo tableFieldInfo, final String prefix) {
        final String newPrefix = prefix == null ? EMPTY : prefix;
        String sqlScript = " AND " + String.format(SqlCondition.EQUAL, String.format("%s.%s",tableInfo.getTableName(),tableFieldInfo.getColumn()), newPrefix + tableFieldInfo.getEl());
        if (tableFieldInfo.getWhereStrategy()== FieldStrategy.NEVER) {
            return null;
        }
        if (tableFieldInfo.getWhereStrategy() == FieldStrategy.IGNORED) {
            return sqlScript;
        }
        if (tableFieldInfo.getWhereStrategy() == FieldStrategy.NOT_EMPTY && tableFieldInfo.isCharSequence()) {
            return SqlScriptUtils.convertIf(sqlScript, String.format("%s != null and %s != ''",  tableFieldInfo.getProperty(),  tableFieldInfo.getProperty()),
                    false);
        }
        return SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", tableFieldInfo.getProperty()), false);

    }
    protected String sqlWhereEntityWrapper(boolean newLine, TableInfo table) {
        String sqlScript;
        if (table.isLogicDelete()) {
            sqlScript = getAllSqlWhere(table,true, true, WRAPPER_ENTITY_DOT);
            sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", WRAPPER_ENTITY),
                    true);
            sqlScript += (NEWLINE + table.getLogicDeleteSql(true, true) + NEWLINE);
            String normalSqlScript = SqlScriptUtils.convertIf(String.format("AND ${%s}", WRAPPER_SQLSEGMENT),
                    String.format("%s != null and %s != '' and %s", WRAPPER_SQLSEGMENT, WRAPPER_SQLSEGMENT,
                            WRAPPER_NONEMPTYOFNORMAL), true);
            normalSqlScript += NEWLINE;
            normalSqlScript += SqlScriptUtils.convertIf(String.format(" ${%s}", WRAPPER_SQLSEGMENT),
                    String.format("%s != null and %s != '' and %s", WRAPPER_SQLSEGMENT, WRAPPER_SQLSEGMENT,
                            WRAPPER_EMPTYOFNORMAL), true);
            sqlScript += normalSqlScript;
            sqlScript = SqlScriptUtils.convertChoose(String.format("%s != null", WRAPPER), sqlScript,
                    table.getLogicDeleteSql(false, true));
            sqlScript = SqlScriptUtils.convertWhere(sqlScript);
            String deleteColumn = table.getLogicDeleteFieldInfo().getColumn();
            return newLine ? NEWLINE + sqlScript.replace(deleteColumn,table.getTableName()+"."+ deleteColumn) : sqlScript.replace(deleteColumn,table.getTableName()+"."+ deleteColumn);
        }else{
            sqlScript = table.getAllSqlWhere(false, true, "ew.entity.");
            sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", "ew.entity"), true);
            sqlScript = sqlScript + "\n";
            sqlScript = sqlScript + SqlScriptUtils.convertIf(String.format(SqlScriptUtils.convertIf(" AND", String.format("%s and %s", "ew.nonEmptyOfEntity", "ew.nonEmptyOfNormal"), false) + " ${%s}", "ew.sqlSegment"), String.format("%s != null and %s != '' and %s", "ew.sqlSegment", "ew.sqlSegment", "ew.nonEmptyOfWhere"), true);
            sqlScript = SqlScriptUtils.convertWhere(sqlScript) + "\n";
            sqlScript = sqlScript + SqlScriptUtils.convertIf(String.format(" ${%s}", "ew.sqlSegment"), String.format("%s != null and %s != '' and %s", "ew.sqlSegment", "ew.sqlSegment", "ew.emptyOfWhere"), true);
            sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", "ew"), true);
            return newLine ? "\n" + sqlScript : sqlScript;        }
    }

    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        String sql;
        this.joinClumnConfig=new JoinColumnConfig(builderAssistant,modelClass);
        joinClumnConfig.resolve();
        MySqlMethod sqlMethod = MySqlMethod.SELECT_JOIN_PAGE;
        sql = String.format(sqlMethod.getSql(), sqlSelectColumns(tableInfo, true),
                tableStr, sqlWhereEntityWrapper(true, tableInfo), sqlComment());
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        doCreateList(mapperClass,modelClass);
        addListStatement(MySqlMethod.SELECT_JOIN_LIST,sqlSource,tableInfo,mapperClass,modelClass);
        return this.addSelectMappedStatementForTable(mapperClass, sqlMethod.getMethod(), sqlSource, tableInfo,modelClass);
    }

    private void doCreateList(Class<?> mapperClass,Class<?> modelClass){
        String method = MySqlMethod.SELECT_ONETOMANY_LIST.getMethod();
        String sql = MySqlMethod.SELECT_ONETOMANY_LIST.getSql();
        JoinColumnConfig.oneToManyMap.get(modelClass).forEach((k, v)->{
            String trueSql=String.format(sql,v.getSql(),v.getJoinTableInfo().getTableName(),v.getJoinClumn());
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, trueSql, modelClass);
            this.addSelectListMappedStatementForTable(mapperClass, String.format(method,v.getFieldName()), sqlSource, v.getJoinTableInfo(),modelClass,v.getFieldName());
        });
    }

    private void addListStatement(MySqlMethod mySqlMethod, SqlSource sqlSource, TableInfo tableInfo, Class mapperClass, Class modelClass){
        this.addSelectMappedStatementForTable(mapperClass, mySqlMethod.getMethod(), sqlSource, tableInfo,modelClass);
    }

    protected MappedStatement addSelectMappedStatementForTable(Class<?> mapperClass, String id, SqlSource sqlSource, TableInfo table, Class modelClass) {
        String resultMapId=myMapperRegister.registerResultMap(configuration,mapperClass,modelClass,table);
        String resultMap =resultMapId.substring(resultMapId.lastIndexOf(".")+1);
        return addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.SELECT, null,
                resultMap, null, new NoKeyGenerator(), null, null);
    }
    private MappedStatement addSelectListMappedStatementForTable(Class<?> mapperClass, String id, SqlSource sqlSource, TableInfo table, Class modelClass,String fieldName) {
        return addSelectMappedStatementForOther(mapperClass, id, sqlSource, table.getEntityType());
    }
    private String createSqlSelectClumn(String clumnStr, TableInfo tableInfo){
        //todo
        List<String> clumns=new ArrayList<>();
        StringBuilder tableStr=new StringBuilder();
        tableStr.append(tableInfo.getTableName()+" "+StrUtil.toCamelCase(tableInfo.getTableName()));
        JoinColumnConfig.fieldsMap.get(tableInfo.getEntityType()).forEach((k, v)->{
            if(v.getJoinClumnConfig().getUseJavaType()){
                clumns.add(v.getSql());
            }else{
                clumns.add("CONCAT_WS('"+ JoinColumnConfig.separato+"',"+v.getSql()+") as " +v.getFieldName()+MyMapperRegister.placeholder);
            }
            tableStr.append(leftJoinStr(tableInfo,v.getJoinClumn(),v.getJoinTableClumn(),v.getFieldName(),v.getJoinTableInfo()));
        });
        this.tableStr=tableStr.toString();
        String joinTable= String.join(",",clumns);
        if(StringUtils.isNotBlank(joinTable)){
            return clumnStr.replace("*", StrUtil.toCamelCase(tableInfo.getTableName())+".*,"+joinTable);
        }
        return clumnStr;
    }

    private String leftJoinStr(TableInfo tableInfo, String joinClumn, String joinTableClumn, String fieldName, TableInfo clumnTableInfo){
        if (joinTableClumn.contains(",")) {
            StringBuilder onBu = new StringBuilder();
            String onS = " %s %s.%s=%s.%s";
            String[] split = joinTableClumn.split(",");
            for (int i = 0; i < split.length; i++) {
                if (i == 0) {
                    onBu.append(String.format(onS, "on", StrUtil.toCamelCase(tableInfo.getTableName()),
                            joinClumn, fieldName, split[i]));
                } else {
                    onBu.append(String.format(onS, "and", tableInfo.getTableName(),
                            split[i], fieldName, split[i]));
                }
            }
            String joinStr = " left join %s %s %s";
            return String.format(joinStr, clumnTableInfo.getTableName(), fieldName, onBu.toString());
        } else {
            String joinStr = " left join %s %s on %s.%s=%s.%s";
            return String.format(joinStr, clumnTableInfo.getTableName(), fieldName, StrUtil.toCamelCase(tableInfo.getTableName()),
                    joinClumn, fieldName, StringUtils.isNotBlank(joinTableClumn) ? joinTableClumn : clumnTableInfo.getKeyColumn());
        }

    }
}
